package ar.edu.itba.pod.Legajo47342;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import ar.edu.itba.pod.Legajo47342.communication.ConnectionManagerImpl;
import ar.edu.itba.pod.Legajo47342.communication.payload.NodeAgentLoadRequestPayloadImpl;
import ar.edu.itba.pod.Legajo47342.market.MarketManagerImpl;
import ar.edu.itba.pod.Legajo47342.simulation.SimulationManagerImpl;
import ar.edu.itba.pod.simul.communication.AgentDescriptor;
import ar.edu.itba.pod.simul.communication.ConnectionManager;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.communication.MessageType;
import ar.edu.itba.pod.simul.communication.NodeAgentLoad;
import ar.edu.itba.pod.simul.communication.ReferenceName;
import ar.edu.itba.pod.simul.communication.SimulationCommunication;
import ar.edu.itba.pod.simul.market.MarketManager;
import ar.edu.itba.pod.simul.simulation.SimulationManager;

public class Node implements ReferenceName{
	private ConnectionManager manager;
	private InetAddress address;
	private int port;
	private String nodeId;
	private String groupId;
	private Registry registry;
	private Set<String> clusterNodes;
	private Map<Long,Message>broadcastMessages;
	private Map<String,Long> lastMessageTime;
	private Map<String,NodeAgentLoad> loads;
	private SimulationManager simulManager;
	private Set<AgentDescriptor> agents;
	private MarketManager marketManager;

	public static final int DEFAULT_PORT = 1099;
	public static final long TRANS_WAIT = 1000;
	public static final long WHOLE_TRANS_WAIT = 5000;
	public static final long LOAD_REQUEST_RESPONSE_TIME = 5000;
	public static final long TPCWAIT = 5000;
	
	public Node(String id) throws UnknownHostException{
		super();
		
		this.nodeId = id;
		this.groupId = null;
		this.address = InetAddress.getByName(id);
		this.port = DEFAULT_PORT;
		this.setClusterNodes(new HashSet<String>());
		this.broadcastMessages = new HashMap<Long, Message>();
		this.lastMessageTime = new HashMap<String, Long>();
		this.loads = new HashMap<String, NodeAgentLoad>();
		this.agents = new HashSet<AgentDescriptor>();
		this.marketManager = new MarketManagerImpl(this);
		
		this.loads.put(id, new NodeAgentLoad(id, 0));
		
		try {
			this.manager = new ConnectionManagerImpl(this);
		} catch (RemoteException e) {
			e.printStackTrace();
		}
		
		this.simulManager = new SimulationManagerImpl(this);
		
	}
	
	public ConnectionManager getManager() {
		return manager;
	}

	public void setClusterNodes(Set<String> clusterNodes) {
		this.clusterNodes = clusterNodes;
	}

	public Set<String> getClusterNodes() {
		return clusterNodes;
	}

	public String getNodeId() {
		return nodeId;
	}
	
	public Set<String> getRandomNodes(String nodeId){
		
		Set<String> ans = new HashSet<String>();
		ans.addAll(clusterNodes);
		ans.remove(nodeId);
		
		return ans;
//		Set<String> ans = new HashSet<String>();
//		Random r = new Random();
//		double value = r.nextDouble();
//		
//		for (String n : clusterNodes) {
//			if(value > r.nextDouble()){
//				ans.add(n);
//			}
//		}
//		
//		ans.remove(nodeId);
//		
//		return ans;
	}

	public void setGroupId(String groupId) {
		this.groupId = groupId;
	}

	public String getGroupId() {
		return groupId;
	}

	public Map<Long,Message> getMessages() {
		return broadcastMessages;
	}
	
	public long getLastTime(String nodeId){
		Long ans = lastMessageTime.get(nodeId);
		
		if(ans !=null){
			return ans;
		}else {
			return 0;
		}
	}
	
	public Map<String, Long> getLastMessageTime() {
		return lastMessageTime;
	}

	public Map<String, NodeAgentLoad> getLoads() {
		return loads;
	}

	public SimulationManager getSimulManager() {
		return simulManager;
	}

	public MarketManager getMarketManager() {
		return marketManager;
	}

	public void balanceViejo() {
		updateLoads();
		List<AgentDescriptor> allDesc = new ArrayList<AgentDescriptor>();
		//tomo todos los descriptors de todos los nodos
		for (NodeAgentLoad l : loads.values()) {
			try {
				allDesc.addAll(getManager().getConnectionManager(l.getNodeId()).
						getSimulationCommunication().migrateAgents(l.getNumberOfAgents()));
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		
		int descCant = allDesc.size();
		int nodesCant = loads.keySet().size();
		int agentsPerNode = (int) Math.floor(descCant/nodesCant);
		int remainingAgents = descCant - (agentsPerNode * nodesCant);
		int i;
		SimulationCommunication sCom;
		
		try {
			for (String n : loads.keySet()) {
				sCom = getManager().getConnectionManager(n)
						.getSimulationCommunication();
				for (i = 0; i < agentsPerNode; i++) {
					sCom.startAgent(allDesc.remove(0));
				}
			}
			
			if (remainingAgents != allDesc.size()) {
				System.out.println("Las cuentas estan mal!!!!");
			}
			
			while (allDesc.size() > 0) {
				for (String n : loads.keySet()) {
					sCom = getManager().getConnectionManager(n)
					.getSimulationCommunication();
					
					if (allDesc.size() != 0) {
						sCom.startAgent(allDesc.remove(0));
					}else {
						return;
					}
					
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	public void balance(){
		balanceWithAgents(null);
		
	}
	
	public void updateLoads(){
		Message req = new Message(getNodeId(),System.currentTimeMillis(),
				MessageType.NODE_AGENTS_LOAD_REQUEST,new NodeAgentLoadRequestPayloadImpl());
		try {
			getManager().getGroupCommunication().broadcast(req);
		} catch (RemoteException e1) {
			e1.printStackTrace();
		}
		
		try {
			Thread.sleep(LOAD_REQUEST_RESPONSE_TIME);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public Set<AgentDescriptor> getAgents() {
		return agents;
	}

	public void balanceWithAgents(Set<AgentDescriptor> agents2) {
		updateLoads();
		
		int cantAgents = 0;
		
		for (NodeAgentLoad l : loads.values()) {
			cantAgents += l.getNumberOfAgents();
		}
		if (agents2 != null) {
			cantAgents += agents2.size();
		}
		
		int nodesCant = loads.keySet().size();
		if (nodesCant == 0 || cantAgents == 0) {
			return;
		}
		
		int agentsPerNode = (int) Math.floor(cantAgents/nodesCant);
		int remainingAgents = cantAgents - (agentsPerNode * nodesCant);
		
		if (agentsPerNode == 0) {
			agentsPerNode = 1;
			remainingAgents = 0;
		}
		
		int toMigrate;
		List<AgentDescriptor> descToReasign = new ArrayList<AgentDescriptor>();
		
		for (String n : loads.keySet()) {
			toMigrate = loads.get(n).getNumberOfAgents() - agentsPerNode;
			if (toMigrate > 0) {
				try {
					descToReasign.addAll(getManager().getConnectionManager(n).
							getSimulationCommunication().migrateAgents(toMigrate));
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
		
		if (agents2 != null) {
			descToReasign.addAll(agents2);
		}
		
		for (String n : loads.keySet()) {
			toMigrate = loads.get(n).getNumberOfAgents() - agentsPerNode;
			while(toMigrate < 0) {
				
				try {
					if (descToReasign.size() == 0) {
						return;
					}
					
					getManager().getConnectionManager(n)
				.getSimulationCommunication().startAgent(descToReasign.remove(0));
				} catch (RemoteException e) {
					e.printStackTrace();
				}
				toMigrate++;
			}
		}
		
		for (String n : loads.keySet()) {
			if (descToReasign.size() == 0) {
				return;
			}else{
				try {
					getManager().getConnectionManager(n)
					.getSimulationCommunication().startAgent(descToReasign.remove(0));
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
		
		
	}
	
}
